package com.example.ktpspringboot.utils;

import org.springframework.stereotype.Service;

@Service
public class SnowflakeUtil {

    //表示起始时间戳
    private long epoch;

    //节点ID
    private long nodeId;

    //上一次生成ID的时间戳
    private long lastTimestamp = -1L;

    //表示序列号
    private long sequence = 0L;

    //初始化起始时间戳和节点ID
    public SnowflakeUtil(long epoch, long nodeId) {
        this.epoch = epoch;
        this.nodeId = nodeId;
    }

    public SnowflakeUtil() {
    }

    //用于生成唯一ID。首先获取当前的时间戳（毫秒级）。如果当前时间戳小于上一次生成ID的时间戳，表示系统时钟出现问题，抛出异常。

    //synchronized 是Java中的关键字，用于实现线程同步，确保在多线程环境下的原子性操作和互斥访问。
    //当一个方法或代码块被synchronized修饰时，它被称为同步方法或同步块。在同一时间内，只允许一个线程访问被synchronized修饰的代码段，
    //其他线程需要等待。
    public synchronized long generateId() {
        long timestamp = System.currentTimeMillis();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Invalid system clock.");
        }
        /*
         * 如果当前时间戳与上一次生成ID的时间戳相同，说明在同一毫秒内生成ID，需要递增序列号。序列号部分使用& 4095操作保证不超过12位（2^12-1）
         * 。如果序列号达到上限（4095），则需要等待下一毫秒再生成ID，调用tilNextMillis方法等待下一毫秒。
         *
         */
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & 4095;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            /*
             * 如果当前时间戳与上一次生成ID的时间戳不同，说明已经进入下一毫秒，序列号重置为0。*/
            sequence = 0L;
        }

        /*
         * 更新上一次生成ID的时间戳为当前时间戳。
           返回生成的唯一ID，根据Snowflake算法的规则，
         * 将时间戳部分左移22位，节点ID部分左移12位，再按位或操作将它们合并，然后加上序列号部分，得到最终的64位ID。*/
        lastTimestamp = timestamp;

        return ((timestamp - epoch) << 22) | (nodeId << 12) | sequence;

    }

    /*
    tilNextMillis方法用于等待下一毫秒的时间戳。
    如果当前时间戳小于等于上一次生成ID的时间戳，就一直循环获取当前时间戳，直到大于上一次时间戳为止，并返回该时间戳。
     * */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}
